-module(erlua_agent).
-author("cmj").

-behaviour(gen_server).

%% API
-export([
    start/1,
    start/2,
    call_func/3,
    send_func/3,
    call_func_ls/3,
    send_func_ls/3,
    call_load/2,
    send_load/2
]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
    code_change/3]).

-define(SERVER, ?MODULE).

-record(state_ref, {state_ref}).

%%%===================================================================
%%% API
%%%===================================================================
start(Name) ->
    gen_server:start({local, Name}, ?MODULE, [], []).

start(Name, LuaMod) ->
    gen_server:start({local, Name}, ?MODULE, [LuaMod], []).

call_load(Pid, LuaMod) ->
    gen_server:call(Pid, {?FUNCTION_NAME, LuaMod}).

call_func(Pid, FuncName, Args) ->
    gen_server:call(Pid, {?FUNCTION_NAME, FuncName, Args}).

call_func_ls(Pid, FuncName, Args) ->
    gen_server:call(Pid, {?FUNCTION_NAME, FuncName, Args}).

send_load(Pid, LuaMod) ->
    erlang:send(Pid, {?FUNCTION_NAME, LuaMod}).

send_func(Pid, FuncName, Args) ->
    erlang:send(Pid, {?FUNCTION_NAME, FuncName, Args}).

send_func_ls(Pid, FuncName, Args) ->
    erlang:send(Pid, {?FUNCTION_NAME, FuncName, Args}).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
    {ok, StateRef} = erlua:newstate(),
    {ok, #state_ref{state_ref = StateRef}};
init([LuaMod]) when is_binary(LuaMod) orelse is_bitstring(LuaMod) orelse is_atom(LuaMod) ->
    {ok, StateRef} = erlua:newstate(),
    erlua:load_luafile(StateRef, LuaMod),
    {ok, #state_ref{state_ref = StateRef}}.

handle_call({load_module, LuaMod}, _From, State = #state_ref{state_ref = LuaState}) ->
    Result = erlua:load_luafile(LuaState, LuaMod),
    {reply, Result, State#state_ref{state_ref = LuaState}};
handle_call({func, FuncName, Args}, _From, State = #state_ref{state_ref = LuaState}) ->
    Result = do_func_handle(LuaState, FuncName, Args),
    {reply, Result, State};
handle_call({func_ls, FuncName, Args}, _From, State = #state_ref{state_ref = LuaState}) ->
    Result = erlua:func_ls(LuaState, FuncName, Args),
    {reply, Result, State};
handle_call(_Request, _From, State = #state_ref{}) ->
    {reply, ok, State}.

handle_cast({load_module, LuaMod}, State = #state_ref{state_ref = LuaState}) ->
    erlua:load_luafile(LuaState, LuaMod),
    {noreply, State};
handle_cast({func, FuncName, Args}, State = #state_ref{state_ref = LuaState}) ->
    do_func_handle(LuaState, FuncName, Args),
    {noreply, State};
handle_cast({func_ls, FuncName, Args}, State = #state_ref{state_ref = LuaState}) ->
    erlua:func_ls(LuaState, FuncName, Args),
    {noreply, State};
handle_cast(_Request, State = #state_ref{}) ->
    {noreply, State}.

handle_info({load_module, LuaMod}, State = #state_ref{state_ref = LuaState}) ->
    erlua:load_luafile(LuaState, LuaMod),
    {noreply, State};
handle_info({func, FuncName, Args}, State = #state_ref{state_ref = LuaState}) ->
    do_func_handle(LuaState, FuncName, Args),
    {noreply, State};
handle_info({func_ls, FuncName, Args}, State = #state_ref{state_ref = LuaState}) ->
    erlua:func_ls(LuaState, FuncName, Args),
    {noreply, State};
handle_info(_Info, State = #state_ref{}) ->
    {noreply, State}.

terminate(_Reason, _State = #state_ref{}) ->
    ok.

%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state_ref{},
    Extra :: term()) ->
    {ok, NewState :: #state_ref{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state_ref{}, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
do_func_handle(LuaState, FuncName, Args) ->
    NewArgs = [FuncName | Args],
    NewArgs2 = [LuaState | NewArgs],
    Result = erlang:apply(erlua, func, NewArgs2),
    Result.
